/** @file md5_object.cpp
    @brief MD5 model object */

#ifndef G3_MD5_OBJECT_H
#define G3_MD5_OBJECT_H

#include "material.h"

#include "math/m_vector.h"
#include "math/m_quaternion.h"
#include "math/m_bounding.h"

#include "file/f_stream.h"
#include "text.h"

#include <string>
#include <map>

namespace G3 {

    class MD5_MODEL {
    public:

        MD5_MODEL ();
        ~MD5_MODEL ();

        class BASEFRAME_JOINT {
        public:
            BASEFRAME_JOINT () {}
            ~BASEFRAME_JOINT () {}

            BASEFRAME_JOINT& operator= (const BASEFRAME_JOINT &bfj);

            VEC3F position;
            QUATERNIONF orientation;

            void Dump (std::string indent, LOG *log);
        };

        class JOINT {
        public:
            JOINT ();
            ~JOINT ();

            std::string name;
            int parent;

            VEC3F position;
            QUATERNIONF orientation;

            void Dump (std::string indent, LOG *log);
        };

        class VERTEX {
        public:
            VERTEX ();
            ~VERTEX () {}

            VEC2F texCoord;

            int weightStart;
            int numWeights;

            void Dump (std::string indent, LOG *log);
        };

        class TRIANGLE {
        public:
            TRIANGLE ();
            ~TRIANGLE ();

            int indices [3];

            void Dump (std::string indent, LOG *log);
        };

        class WEIGHT {
        public:
            WEIGHT ();
            ~WEIGHT ();

            int joint;
            float bias;

            VEC3F position;
            VEC3F normal;
            VEC3F tangent;

            void Dump (std::string indent, LOG *log);
        };

        class SKELETON {
        public:
            SKELETON ();
            ~SKELETON ();

            LOG *log;

            std::vector<JOINT *> joints;

            SKELETON *Clone () const;

            bool Parse (STREAM *stream, int numJoints);

            void Dump (std::string indent, LOG *log);
        };

        class MESH {
        public:
            MESH ();
            ~MESH ();

            LOG *log;

            std::string name;
            std::string materialName;

            bool visible;

            AABB3F bbox;

            std::vector <VERTEX *> preVerts;
            std::vector <TRIANGLE *> triangles;
            std::vector <WEIGHT *> weights;

            float *vertices;
            float *normals;
            float *tangents;
            float *texcoords;
            int *indices;

            int numVertices;
            int numTriangles;
            int numWeights;

            MATERIAL material;

            void Render ();

            void SetupTextures ();
            void CalcVertices (SKELETON *skeleton);
            void CalcWeightNormals (SKELETON *skeleton);
            void CalcWeightTangents (SKELETON *skeleton);
            void CalcBBox (SKELETON *skeleton);
            void SetupIndices ();

            bool Parse (STREAM *stream);

            void Dump (std::string indent, LOG *log);
        };

        class ANIMATION {
        public:
            ANIMATION ();
            ~ANIMATION ();

            class JOINT_INFO {
            public:
                JOINT_INFO ();
                ~JOINT_INFO ();

                JOINT_INFO& operator= (const JOINT_INFO &ji);

                enum JOINT_FLAG {
                    JF_TX = 1,
                    JF_TY = 2,
                    JF_TZ = 4,
                    JF_QX = 8,
                    JF_QY = 16,
                    JF_QZ = 24
                };

                std::string name;
                int parent;

                short flags;
                int startIndex;

                void Dump (std::string indent, LOG *log);
            };

            LOG *log;

            std::string name;

            int numFrames;
            int framerate;

            int numAnimatedComponents;
            int numJoints;

            std::vector<SKELETON *> skeletonFrames;
            std::vector<G3::AABB3F *> bboxes;

            std::vector<JOINT_INFO> jointInfos;
            std::vector<BASEFRAME_JOINT> baseFrame;
            std::vector<float> animData;

            void Interpolate (int frameA, int frameB, float factor, SKELETON *skel);
            bool BuildFrameSkeleton ();

            bool ParseHierarchy (STREAM *stream);
            bool ParseBounds (STREAM *stream);
            bool ParseBaseFrame (STREAM *stream);
            bool ParseFrame (STREAM *stream);
            bool ParseAnimation (STREAM *stream);

            void Dump (std::string indent, LOG *log);
        };

        LOG *log;

        SKELETON *baseSkeleton;
        std::vector<MESH *> meshes;
        std::map<std::string, ANIMATION *> animationMap;

        int numJoints;
        int numMeshes;

        bool ParseMesh (STREAM *stream);

        bool LoadMesh (const std::string &filename);
        bool LoadAnimation (const std::string &filename, const std::string &animName);
        bool ValidityCheck (ANIMATION *anim) const;

        ANIMATION *GetAnimation (const std::string &animName);
        MESH *GetMesh (const std::string &meshName);

        void Prepare (SKELETON *skeleton);

        void Render ();
    };

    class MD5_OBJECT {
    public:
        MD5_OBJECT ();
        ~MD5_OBJECT ();

        LOG *log;

        MD5_MODEL *model;
        MD5_MODEL::SKELETON *skeleton;
        MD5_MODEL::ANIMATION *animation;

        int curFrame;
        int nextFrame;

        double lastTime;
        double maxTime;

        bool LoadMesh (const std::string &filename);
        bool LoadAnimation (const std::string &filename, const std::string &name);

        void SetAnimation (const std::string &animName);

        void Animate (double elapsedTime);
        void Prepare ();

        void Render (double elapsedTime);
    };

}

#endif
